package xconf

import (
	"bufio"
	"io"
	"os"
	"strconv"
	"strings"

	. "gitee.com/shuohe/xutils"
	"gitee.com/shuohe/xutils/xerror"
)

type ConfigObject map[string]interface{}

func errWrapWalk(err error, fieldPath string) error {
	return xerror.SimpleWrapf(err, Caller(2)+": Can not get value of field '%s'.", fieldPath)
}

func (this ConfigObject) walk(fieldPath string) (interface{}, error) {
	fields := strings.Split(fieldPath, ".")

	var result interface{} = this
	for i := 0; i < len(fields); i++ {
		f := fields[i]
		if IsDigit(f[0]) {
			// 当前的 result 应当是一个数组
			ar, ok := result.([]ConfigObject)
			if !ok {
				return nil, xerror.Newf("The value of field '%s' is not an array.", fields[i-1])
			}
			index, err := strconv.Atoi(f)
			if err != nil {
				return nil, xerror.Newf("'%s' is an array and has no subfield '%s'.", fields[i-1], f)
			}
			result = ar[index]
		} else {
			// 当前的 result 应当是一个对象
			o, ok := result.(ConfigObject)
			if !ok {
				return nil, xerror.Newf("The value of field '%s' is not an object.", fields[i-1])
			}
			result, ok = o[f]
			if !ok {
				return nil, xerror.Newf("Field '%s' doesn't exist.", f)
			}
		}
	}

	return result, nil
}

func (this ConfigObject) GetString2(fieldPath string) string {
	value, err := this.walk(fieldPath)
	if err != nil {
		panic(errWrapWalk(err, fieldPath))
	}

	result, ok := value.(string)
	if !ok {
		panic(xerror.Newf("The value of field '%s' is not a string.", fieldPath))
	}

	return result
}

func (this ConfigObject) GetObject2(fieldPath string) ConfigObject {
	value, err := this.walk(fieldPath)
	if err != nil {
		panic(errWrapWalk(err, fieldPath))
	}

	result, ok := value.(ConfigObject)
	if !ok {
		panic(xerror.Newf("The value of field '%s' is not an object.", fieldPath))
	}

	return result
}

func (this ConfigObject) GetArray2(fieldPath string) []ConfigObject {
	value, err := this.walk(fieldPath)
	if err != nil {
		panic(errWrapWalk(err, fieldPath))
	}

	result, ok := value.([]ConfigObject)
	if !ok {
		panic(xerror.Newf("The value of field '%s' is not an array.", fieldPath))
	}

	return result
}

func (this ConfigObject) Iterate(action func(string, interface{}) bool) {
	for field, value := range this {
		if !action(field, value) {
			break
		}
	}
}

func installFieldValue(indent int, cfgObj ConfigObject, field string, value string) error {
	if value == "{}" {
		// 需要设置的是一个对象

		// 该字段必须之前不存在
		if cfgObj[field] != nil {
			return xerror.Newf("Field '%s' already exists.", field)
		}
		// 将该字段设为一个空白对象
		obj := make(ConfigObject)
		cfgObj[field] = obj
		// 为这个对象添加字段
		err := parseFieldAtIndent(indent+1, obj)
		if err != nil {
			return xerror.Wrapf(err, "Syntax error at line %d.", g_lineNumber)
		}
	} else if value == "[{}]" {
		// 需要设置的是一个对象数组

		if fv := cfgObj[field]; fv != nil {
			// 如果该字段存在，则必须是一个对象数组
			objArray, ok := fv.([]ConfigObject)
			if !ok {
				return xerror.Newf("Field '%s' already exists.", field)
			}
			// 在数组上增加一个空白对象
			obj := make(ConfigObject)
			cfgObj[field] = append(objArray, obj)
			// 为这个对象添加字段
			err := parseFieldAtIndent(indent+1, obj)
			if err != nil {
				return xerror.Wrapf(err, "Syntax error at line %d.", g_lineNumber)
			}
		} else {
			// 若该字段不存在
			// 该字段被初始化为一个对象数组，并创建第一个对象
			objArray := make([]ConfigObject, 1, 8)
			objArray[0] = make(ConfigObject)
			cfgObj[field] = objArray
			// 为这个对象添加字段
			err := parseFieldAtIndent(indent+1, objArray[0])
			if err != nil {
				return xerror.Wrapf(err, "Syntax error at line %d.", g_lineNumber)
			}
		}
	} else {
		// 需要设置的是一个普通字符串
		cfgObj[field] = value
	}

	return nil
}

func parseFieldAtIndent(indent int, cfgObj ConfigObject) error {
	// 只要预读行读取正确且缩进层次等于期望值，就继续处理
	for g_scanErr == nil && g_indent == indent {
		// 提取字段名
		start, err := getIdentifier(g_line, 0)
		if err != nil {
			return xerror.Wrap(err, "Need valid identifier.")
		}

		// 匹配 ':' 或 '='
		start, err = matchSeperator(g_line, start)
		if err != nil {
			return xerror.Wrap(err, "Need seperator.")
		}

		// 剩下的内容就是数据
		value := strings.TrimSpace(g_line[start:])

		// 当前预读行用掉了，预读下一行，供后续处理
		g_indent, g_line, g_scanErr = scanNextLine()

		// 处理字段名和数据
		// fmt.Printf("%d: \"%s\", \"%s\"\n", g_indent, g_identifier, value)
		installFieldValue(indent, cfgObj, g_identifier, value)
	}
	// 预读行读取出现错误，则返回错误
	if g_scanErr != nil && g_scanErr != io.EOF {
		return xerror.Wrapf(g_scanErr, "Read error at line %d.", g_lineNumber)
	}

	// 若预读行的缩进层次大于期望值，是一个错误
	if g_indent > indent {
		return xerror.Newf("Unexpected indent at line %d.", g_lineNumber)
	}

	// 若预读行的缩进层次小于期望值，或到达文件尾，则本层处理结束
	return nil
}

func Load(filename string) (ConfigObject, error) {
	// 打开文件
	f, err := os.Open(filename)
	if err != nil {
		return nil, xerror.Wrap(err, "Can not open file for read.")
	}
	defer f.Close()

	root := make(ConfigObject)

	g_scanner = bufio.NewScanner(f)
	g_lineNumber = 0
	g_indent, g_line, g_scanErr = scanNextLine()

	err = parseFieldAtIndent(0, root)
	if err != nil {
		return nil, xerror.Wrapf(err, "Syntax error at line %d.", g_lineNumber)
	}

	return root, nil
}

func Load2(filename string) ConfigObject {
    co, err := Load(filename)
    if err != nil {
        panic(err)
    }

    return co
}